﻿using System;
using System.Collections.Generic;
using System.Threading;

using SlimDX.DirectInput;

namespace Gamey.Input
{
    public class InputManager : IDisposable
    {
        const int LowJoystickThreshold = 10;
        const int HighJoystickThreshold = 65525;

        DirectInput input;
        Timer timer;
        SynchronizationContext context;

        public event EventHandler Up;
        public event EventHandler Down;
        public event EventHandler Left;
        public event EventHandler Right;
        public event EventHandler<ButtonEventArgs> Button;

        public void Connect()
        {
            this.input = new DirectInput();
            IList<DeviceInstance> controllers = input.GetDevices(DeviceClass.GameController, DeviceEnumerationFlags.AttachedOnly);

            var joysticks = new List<Joystick>();
            foreach (DeviceInstance controller in controllers)
            {
                var joystick = new Joystick(input, controller.InstanceGuid);
                if (joystick.Acquire().IsSuccess)
                {
                    joysticks.Add(joystick);
                }
            }

            if (joysticks.Count > 0)
            {
                this.context = GetSynchronizationContext();
                this.timer = new Timer(PollGameControllers, joysticks, 100, 100);
            }
        }

        public void Dispose()
        {
            DirectInput currentInput = Interlocked.Exchange(ref this.input, null);
            if (currentInput != null)
            {
                currentInput.Dispose();
            }
            
            Timer currentTimer = Interlocked.Exchange(ref this.timer, null);
            if (currentTimer != null)
            {
                currentTimer.Dispose();
            }
        }

        private SynchronizationContext GetSynchronizationContext()
        {
            if (SynchronizationContext.Current == null)
            {
                SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
            }
            return SynchronizationContext.Current;
        }

        private void PollGameControllers(object state)
        {
            var joysticks = (IList<Joystick>)state;
            var joystickStates = new List<JoystickState>(joysticks.Count);
            foreach (Joystick joystick in joysticks)
            {
                joystick.Poll();
                joystickStates.Add(joystick.GetCurrentState());
            }

            foreach (JoystickState joystickState in joystickStates)
            {
                RaiseJoystickEvents(joystickState);
            }
        }

        private void RaiseJoystickEvents(JoystickState joystickState)
        {
            bool[] buttons = joystickState.GetButtons();
            for (int i = 0; i < buttons.Length; ++i)
            {
                if (buttons[i])
                {
                    RaiseButtonEvent(Button, i + 1);
                }
            }

            if (joystickState.X < LowJoystickThreshold)
            {
                RaiseEvent(Left);
            }
            else if (joystickState.X > HighJoystickThreshold)
            {
                RaiseEvent(Right);
            }

            if (joystickState.Y < LowJoystickThreshold)
            {
                RaiseEvent(Up);
            }
            else if (joystickState.Y > HighJoystickThreshold)
            {
                RaiseEvent(Down);
            }
        }

        private void RaiseEvent(EventHandler handler)
        {
            if (handler != null)
            {
                this.context.Post(state => handler(this, EventArgs.Empty), null);
            }
        }

        private void RaiseButtonEvent(EventHandler<ButtonEventArgs> handler, int button)
        {
            if (handler != null)
            {
                this.context.Post(state => handler(this, new ButtonEventArgs(button)), null);
            }
        }
    }
}
